// APRS Mobile Tracker
// LuatOS ESP32C3经典版with SKM35 GPS Module
// ESP32C3 Serial is USB Port, Serial1 is GPIO18/RX, 19/TX
// GPS module TX connect to ESP Serial1 RX/GPIO18/;
// Verified by zhaoqm @ 20230715
// 20230909 增加百度鹰眼轨迹上传功能，HTTP POST API接口

#include <Arduino.h>
#include <Adafruit_GPS.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <HTTPClient.h>

#define GPSECHO     false
#define LED4        12
#define LED5        13
#define SSID        "Honor-zhao"
#define PASS        "XXXX"

Adafruit_GPS GPS(&Serial1);    // UART1 RX/IO18 TX/IO19 for GPS module
uint32_t timer1;
uint32_t timer2;
uint32_t timer3;

// 20230909 增加Epoch time获取接口
// get current local time, lib and define
#include "time.h" 
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec      = 3600 * 8;       // 8 Hours
const int   daylightOffset_sec = 3600 * 0;       // no daylight offset
// get current local time, function
time_t getEpochTime()
{
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");
    return 0;
  }

  time_t epochTime;
  time(&epochTime);
  Serial.print("Epoch Time: "); Serial.print(epochTime);
  Serial.println(&timeinfo, "  %A, %B %d %Y %H:%M:%S");
  return epochTime;
}

// 20230909 增加百度鹰眼轨迹上传功能，HTTP POST API接口
// POST: https://yingyan.baidu.com/api/v3/track/addpoint
/*
    "ak",your_ak
    "service_id",237918
    "entity_name","BA7MZO"
    "latitude",21.276161            // 纬度  XX.XXXX Degree
    "longitude",110.359064          // 经度 XXX.XXXX Degree
    "loc_time",loc_time             // 定位时间戳，精确到秒
    "coord_type_input","wgs84"      // 坐标，wgs84：GPS坐标， gcj02：国测局加密坐标， bd09ll：百度经纬度坐标
    "speed",10.23                   // 速度，单位：km/h
    "direction",15                  // 方向，取值规则： 范围为[0,359]，0度为正北方向，顺时针
*/
String makePostPayload(time_t epochTime,nmea_float_t lat,nmea_float_t lon,nmea_float_t speed,nmea_float_t angle)
{
    nmea_float_t temp;
    nmea_float_t tempLat = int(lat / 100.0);
    temp = lat - tempLat * 100;
    tempLat = tempLat + temp / 60.0;

    nmea_float_t tempLon = int(lon / 100.0);
    temp = lon - tempLon * 100;
    tempLon = tempLon + temp / 60.0;    
    
    String payload = "ak=yzFAVTi7l9HmSFg2BoOdSRZurOhr964Y&service_id=237918&entity_name=BA7MZO";
    payload += "&loc_time=";
    payload += String(epochTime);
    payload += "&latitude=";

    payload += String(tempLat,6);       // 保留6位小数
    payload += "&longitude=";
    payload += String(tempLon,6);       // 保留6位小数
    payload += "&speed=";
    payload += String(speed);
    payload += "&direction=";
    payload += String(int(angle));
    payload += "&coord_type_input=wgs84";

    Serial.println(payload.c_str());
    return payload;
}

// 20230909 增加百度鹰眼轨迹上传功能，HTTP POST API接口
void addpointYingyan() 
{
  // wait for WiFi connection
  if ((WiFi.status() == WL_CONNECTED)) 
  {
    time_t epochTime = getEpochTime();
    String payloadPost = makePostPayload(epochTime,GPS.latitude,GPS.longitude,GPS.speed,GPS.angle);

    HTTPClient http;
    WiFiClient client;

    // configure traged server and url
    http.begin(client, "http://yingyan.baidu.com/api/v3/track/addpoint");  
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");

    Serial.print("[HTTP] POST...\n");
    // start connection and send HTTP header and body
    int httpCode = http.POST(payloadPost);
    // httpCode will be negative on error
    if (httpCode > 0) 
    {
      // HTTP header has been send and Server response header has been handled
      Serial.printf("[HTTP] POST... code: %d\n", httpCode);

      // file found at server
      if (httpCode == HTTP_CODE_OK) 
      {
        const String& payload = http.getString();
        Serial.println("received payload:\n<<");
        Serial.println(payload);
        Serial.println(">>");
      }
    } 
    else 
    {
      Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }

    http.end();
  }
}

/* 
  http://www.aprs-is.net/Connecting.aspx
  Once you have connected to a server TCP port, you must log in after the server's 
    identification line is displayed (see Server Design ). 
  The login is formatted ([] is optional, underlined words are keywords):
    user mycall[-ss] pass passcode[ vers softwarename softwarevers[ UDP udpport][ servercommand]]
*/  
// Data frame:
//            -- c000s000g000t086r000p000h53b10020
//            -- 每秒输出35个字节，包括数据末尾的换行符（OD,OA）
//
//            -- 数据解析：
//            -- c000：风向角度，单位：度。
//            -- s000：前1分钟风速，单位：英里每小时
//            -- g000：前5分钟最高风速，单位：英里每小时
//            -- t086：温度（华氏）
//            -- r000：前一小时雨量（0.01英寸）
//            -- p000：前24小时内的降雨量（0.01英寸）
//            -- h53：湿度（00％= 100％）
//            -- b10020：气压（0.1 hpa）
/*
    snprintf(senddata, sizeof(senddata), "BG8JUN-0>AP51DW,qAS,:=2932.13N/10636.25E_000/000g000t0%dr000p000h%db09691 cyqsd's APRS use ESP32 144.800Mhz~ 3.3V\r\n", temperaturef, humidity);
    client.print(senddata);//向服务器反馈信息
*/

const char *server = "china.aprs2.net";
const char *data1 = "user BA7MZO pass 23713 vers ESP32C3 0.1";
const char *data2 = "BA7MZO-9>APRS,TCPIP*,qAS,BA7MZO:=";                 // 2232.75N/11355.36E;
const char *data3 = "v000/000g000t064r000p000h84b00000 ESP32C3";         // 第一个字符为图标；

void setup()
{
    pinMode(LED4,OUTPUT);
    pinMode(LED5,OUTPUT);
    digitalWrite(LED4,HIGH);
    digitalWrite(LED5,HIGH);
    delay(1000);
    digitalWrite(LED4,LOW);
    Serial.begin(115200);
    Serial.print("\r\nAPRS Track with ESP32C3 from LuatOS... ver ");
    Serial.print(__DATE__); Serial.print(" "); Serial.println(__TIME__);
    
    Serial1.begin(9600, SERIAL_8N1, 18, 19, false); // UART1 RX TX for GPS module

    Serial.println("\r\nStarting ...");
    WiFi.begin(SSID, PASS);
    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(1000);
    }

    Serial.println("");
    Serial.print("Connected to "); Serial.print(SSID); Serial.print(", IP: "); Serial.println(WiFi.localIP());
    digitalWrite(LED5, LOW);
    timer1 = millis();
    timer2 = millis();
    timer3 = millis(); 

    // get current local time, configure the NTP server
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    while(0 == getEpochTime())
    {
       delay(2000);
    }
}

void loop()
{
    char c = GPS.read();
    // if you want to debug, this is a good time to do it!
    if ((c) && (GPSECHO))
        Serial.write(c);

    // if a sentence is received, we can check the checksum, parse it...
    if (GPS.newNMEAreceived())
    {
        // a tricky thing here is if we print the NMEA sentence, or data
        // we end up not listening and catching other sentences!
        // so be very wary if using OUTPUT_ALLDATA and trytng to print out data
        // Serial.println(GPS.lastNMEA());   // this also sets the newNMEAreceived() flag to false

        if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
            return;                     // we can fail to parse a sentence in which case we should just wait for another
    }

    // approximately every 2 seconds or so, print out the current stats
    if ((millis() - timer1) > 2000)
    {
        digitalWrite(LED4, !digitalRead(LED4));
        timer1 = millis(); // reset the timer1

        Serial.print("\nTime: ");
        if (GPS.hour < 10)
        {
            Serial.print('0');
        }
        Serial.print(GPS.hour, DEC);
        Serial.print(':');
        if (GPS.minute < 10)
        {
            Serial.print('0');
        }
        Serial.print(GPS.minute, DEC);
        Serial.print(':');
        if (GPS.seconds < 10)
        {
            Serial.print('0');
        }
        Serial.print(GPS.seconds, DEC);
        Serial.print('.');
        if (GPS.milliseconds < 10)
        {
            Serial.print("00");
        }
        else if (GPS.milliseconds > 9 && GPS.milliseconds < 100)
        {
            Serial.print("0");
        }
        Serial.println(GPS.milliseconds);

        Serial.print("Date: ");
        Serial.print(GPS.day, DEC);
        Serial.print('/');
        Serial.print(GPS.month, DEC);
        Serial.print("/20");
        Serial.println(GPS.year, DEC);

        Serial.print("Fix: ");
        Serial.print((int)GPS.fix);
        Serial.print(", quality: ");
        Serial.println((int)GPS.fixquality);

        if (GPS.fix)
        {
            digitalWrite(LED5, !digitalRead(LED5));

            char loc[64];
            sprintf(loc, "%.2f%c/%.2f%c", GPS.latitude, GPS.lat, GPS.longitude, GPS.lon);   // DDMM.MM N / DDDMM.MM E
            Serial.print("Locat: ");           
            Serial.println(loc);

            Serial.print("Speed: ");
            Serial.print(GPS.speed);
            Serial.print(", Angle: ");
            Serial.println(GPS.angle);

            static float angle_pre = 0;
            uint32_t timeInterval = 120000;       // 正常，每两分钟一次
           
            if(GPS.speed > 16)
                timeInterval = 60000;             // 低速，每1分钟，>30km/h；
        
            if(GPS.speed > 32)
                timeInterval = 30000;             // 中高，每30秒，>60km/h；
            
            if(abs(GPS.angle - angle_pre) > 10)   // 拐弯，每10秒，朝向变化>10度； 
            {
                timeInterval = 10000;       
                angle_pre = GPS.angle;
            }         

            // 按给定时间间隔上传APRS.CN和百度鹰眼
            if ((millis() - timer2) > timeInterval)
            {
                timer2 = millis(); // reset the timer2                                    

                // Connect to the APRS server
                WiFiClient client;
                if (!client.connect(server, 14580))
                {
                    delay(1000);
                    if (!client.connect(server, 14580))
                    {
                        Serial.println("Connect to APRS server failed.");
                        client.stop();
                    }
                }
                else
                {
                    // Login APRS server
                    client.println(data1);
                    Serial.println(data1);

                    char data[128];
                    // Upload APRS Frame
                    sprintf(data, "%s%s%s%c", data2, loc, data3, '\0');
                    Serial.println(data);
                    client.println(data);
                    client.stop();
                }
            }

            // Connect to Baidu Yingyan
            if ((millis() - timer3) > 120000)
            {
                timer3 = millis(); // reset the timer3                
                addpointYingyan();
            }
        }
        else
            digitalWrite(LED5, LOW);
    }
    
}